package com.jxtc.enterprise.common.scheduler;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jxtc.enterprise.common.constants.BatchPrintEnum;
import com.jxtc.enterprise.common.constants.MealTypeEnum;
import com.jxtc.enterprise.common.constants.OrderPaymentStatus;
import com.jxtc.enterprise.common.constants.PrintMethodEnum;
import com.jxtc.enterprise.common.entity.*;
import com.jxtc.enterprise.common.mapper.*;
import com.jxtc.enterprise.common.vo.DeliverBoxApiParamVO;
import com.jxtc.enterprise.employee.serviceImpl.JxOrderServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 定时任务，用于根据 menu_store_config 表中的不同公司在不同店铺的时间配置字段，定时更新订单状态信息
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class DeliveryScheduler {

    private final CompanyMapper companyMapper;
    private final OrderMapper orderMapper;
    private final OrderDetailMapper orderDetailMapper;
    private final JxOrderServiceImpl jxOrderService;
    private final MenuStoreConfigMapper menuStoreConfigMapper;

    /**
     * 每 5 分钟执行一次
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void checkAndUpdateOrderStatus() {
        log.info("定时任务执行...");
        // TODO: 后续需要优化时，将公司相关数据缓存到 Redis 中
        LambdaQueryWrapper<Company> companyWrapper = new LambdaQueryWrapper<Company>().eq(Company::getDelFlag, false);
        List<Company> companyList = companyMapper.selectList(companyWrapper);

        for (Company company : companyList) {
            // 将 company 表中的 delivery_info 字符串转换成 List
            log.info("companyId: {}, 开始进行定时任务", company.getId());

            // SELECT * FROM menu_store_config WHERE company_id = #{companyId} AND del_flag = 0 ORDER BY company_id, store_id DESC;
            // 通过公司 ID，查询出每个公司在不同店铺配置的配送相关信息，
            // 并按照公司、店铺降序排列，方便打印小票信息时，每个公司在同一家店铺的小票一起打印
            LambdaQueryWrapper<MenuStoreConfig> menuStoreConfigQueryWrapper = new LambdaQueryWrapper<MenuStoreConfig>()
                    .eq(MenuStoreConfig::getCompanyId, company.getId())
                    .eq(MenuStoreConfig::getDelFlag, false)
                    .orderByDesc(MenuStoreConfig::getCompanyId, MenuStoreConfig::getStoreId);
            List<MenuStoreConfig> menuStoreConfigList = menuStoreConfigMapper.selectList(menuStoreConfigQueryWrapper);

            for (MenuStoreConfig menuStoreConfig : menuStoreConfigList) {
                log.info("mealType: {}, startTime: {}, endTime: {}, canCancelTime: {}, deliveryTime: {}, printType: {}, printTime: {}",
                        menuStoreConfig.getMealType(), menuStoreConfig.getStartTime(), menuStoreConfig.getEndTime(), menuStoreConfig.getCanCancelTime(),
                        menuStoreConfig.getDeliveryTime(), menuStoreConfig.getPrintType(), menuStoreConfig.getPrintTime());

                // 根据 endTime 将订单状态由 "待发货" -> "待收货"
                changeOrderStatusToReceive(company.getId(), menuStoreConfig.getEndTime(), menuStoreConfig.getMealType());
                // 根据 deliveryTime 将订单状态由 "待收货" -> "已完成"
                changeOrderStatusToCompleted(company.getId(), menuStoreConfig.getDeliveryTime(), menuStoreConfig.getMealType());
                // 判断午餐是否需要批量打印小票，根据 batchPrintTime 更新标志位并调用打印机服务
                changeOrderBatchPrintAndPrintOrder(company.getId(), menuStoreConfig);
            }
            log.info("companyId: {}, 定时任务执行完毕", company.getId());
        }
        log.info("定时任务执行完毕...");
    }

    /**
     * 根据 menu_store_config 表中 endTime 字段判断是否将订单状态由 "待发货" 修改为 "待收货" 状态
     *
     * @param companyId 公司 ID
     * @param endTime   用户可以点餐的截止时间，格式 hh:mm:ss
     */
    private void changeOrderStatusToReceive(String companyId, String endTime, String mealType) {
        log.info("changeOrderStatusToReceive, executing...");

        long endTimestamp = parseTimeToCurrentTimeMillis(endTime);
        long currentTimestamp = System.currentTimeMillis();

        // 通过 mealType 获取其对应的中文用餐时段，用于 OrderPayment 表通过 mealFlag 字段判断用餐时段
        String mealTypeCN = getMealTypeChineseNameFromEnum(mealType);

        // 0 <= 当前时间戳 - 下单截止时间戳 < 5 分钟，修改订单状态为待收货状态
        long round = currentTimestamp - endTimestamp;
        if (round < 5 * 60 * 1000 && round >= 0) {
            LambdaQueryWrapper<OrderPayment> orderQueryWrapper = new LambdaQueryWrapper<OrderPayment>()
                    .eq(OrderPayment::getStatus, OrderPaymentStatus.TO_SEND.getValue())
                    .eq(OrderPayment::getCompanyId, companyId)
                    .like(OrderPayment::getMealFlag, mealTypeCN)
                    .select(OrderPayment::getId);

            LambdaUpdateWrapper<OrderPayment> orderUpdateWrapper = new LambdaUpdateWrapper<OrderPayment>()
                    .eq(OrderPayment::getStatus, OrderPaymentStatus.TO_SEND.getValue())
                    .eq(OrderPayment::getCompanyId, companyId)
                    .like(OrderPayment::getMealFlag, mealTypeCN);

            List<String> orderIdList = orderMapper.selectObjs(orderQueryWrapper);

            // 更新 order_payment 表中的订单状态：待发货 -> 待收货
            OrderPayment orderPayment = new OrderPayment();
            orderPayment.setStatus(OrderPaymentStatus.TO_RECEIVE.getValue());
            int updateCount = orderMapper.update(orderPayment, orderUpdateWrapper);

            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setStatus(orderPayment.getStatus());

            // 批量更新 order_detail 表中的订单状态：待发货 -> 待收货
            if (!orderIdList.isEmpty()) {
                LambdaUpdateWrapper<OrderDetail> orderDetailUpdateWrapper = new LambdaUpdateWrapper<OrderDetail>()
                        .in(OrderDetail::getOrderId, orderIdList)
                        .eq(OrderDetail::getStatus, OrderPaymentStatus.TO_SEND.getValue())
                        .eq(OrderDetail::getMealType, mealType);

                orderDetailMapper.update(orderDetail, orderDetailUpdateWrapper);
            }

            log.info("change order_payment.`status`: 待发货 -> 待收货 (2 -> 3), update count: {}", updateCount);
        }
        log.info("changeOrderStatusToReceive, executed");
    }

    /**
     * 根据 menu_store_config 表中 deliveryTime 字段判断是否将订单状态由 "待收货" 修改为 "已完成"
     *
     * @param companyId    公司 ID
     * @param deliveryTime 订单预计送达时间
     * @param mealType     用餐时段，lunch/dinner 等
     */
    private void changeOrderStatusToCompleted(String companyId, String deliveryTime, String mealType) {
        log.info("changeOrderStatusToCompleted, executing...");

        long arrivalTimestamp = parseTimeToCurrentTimeMillis(deliveryTime);
        long currentTimestamp = System.currentTimeMillis();

        // 通过 mealType 获取其对应的中文用餐时段，用于 OrderPayment 表通过 mealFlag 字段判断用餐时段
        String mealTypeCN = getMealTypeChineseNameFromEnum(mealType);

        // 0 <= 当前时间戳 - 送达时间戳 < 5 分钟，修改订单状态为已完成状态
        long round = currentTimestamp - arrivalTimestamp;
        if (round < 5 * 60 * 1000 && round >= 0) {
            LambdaQueryWrapper<OrderPayment> orderQueryWrapper = new LambdaQueryWrapper<OrderPayment>()
                    .in(OrderPayment::getStatus, OrderPaymentStatus.TO_RECEIVE.getValue(), OrderPaymentStatus.TO_SEND.getValue())
                    .eq(OrderPayment::getCompanyId, companyId)
                    .like(OrderPayment::getMealFlag, mealTypeCN)
                    .select(OrderPayment::getId);
            // 新增需要更新的订单状态，当前时间到了 送达时间时，如果还有订单状态为 2 (待发货)，则表示 将待发货更新待收货的任务有遗漏的，大概率是 业务人员临时修改截止点餐时间（先延长，用户点餐，然后立即改回去），
            // 用户在截止下单后新增的订单，没有办法从 待发货更新为待收货，导致到了送达时间时，还存在 待发货的订单;
            // 此处新逻辑：当到达送达时间点时，将 待发货，待收货的订单，都更新为 已完成; 避免订单状态错误导致的 商户端在第二天还可以看到前一天的订单，以及 用户第二天无法下单，提示限购的问题(由于用户始终存在待发货的订单，占用了用户当天的点餐份额);
            LambdaUpdateWrapper<OrderPayment> orderUpdateWrapper = new LambdaUpdateWrapper<OrderPayment>()
                    .in(OrderPayment::getStatus, OrderPaymentStatus.TO_RECEIVE.getValue(), OrderPaymentStatus.TO_SEND.getValue())
                    .eq(OrderPayment::getCompanyId, companyId)
                    .like(OrderPayment::getMealFlag, mealTypeCN);

            List<String> orderIdList = orderMapper.selectObjs(orderQueryWrapper);

            // 更新 order_payment 表中的订单状态信息
            OrderPayment orderPayment = new OrderPayment();
            orderPayment.setStatus(OrderPaymentStatus.COMPLETED.getValue());
            int updateCount = orderMapper.update(orderPayment, orderUpdateWrapper);

            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setStatus(orderPayment.getStatus());

            // 批量更新 order_detail 表中的订单状态信息
            if (!orderIdList.isEmpty()) {
                LambdaUpdateWrapper<OrderDetail> orderDetailUpdateWrapper = new LambdaUpdateWrapper<OrderDetail>()
                        .in(OrderDetail::getOrderId, orderIdList)
                        .in(OrderDetail::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue())
                        .eq(OrderDetail::getMealType, mealType);

                orderDetailMapper.update(orderDetail, orderDetailUpdateWrapper);
            }

            log.info("changed order_payment.`status`: 待发货/待收货 -> 已完成 (2/3 -> 8), update count: {}", updateCount);
        }
        log.info("changeOrderStatusToCompleted, executed");
    }

    /**
     * 根据 menu_store_config 表中 printType 字段判断是否批量打印订单，
     * 若需要批量打印订单，修改 order_payment 中的 batch_printed 标志位并调用打印机服务
     *
     * @param companyId       公司 ID
     * @param menuStoreConfig 该公司在该店铺的配送信息
     */
    private void changeOrderBatchPrintAndPrintOrder(String companyId, MenuStoreConfig menuStoreConfig) {
        log.info("changeOrderBatchPrintAndPrintOrder, executing...");

        int pritedOrderCount = 0;
        // 如果启用批量打印小票
        String batchPrintTime = menuStoreConfig.getPrintTime();
        if (PrintMethodEnum.BATCH.getName().equals(menuStoreConfig.getPrintType()) && batchPrintTime != null && !batchPrintTime.isEmpty()) {

            long batchPrintTimestamp = parseTimeToCurrentTimeMillis(batchPrintTime);
            long currentTimestamp = System.currentTimeMillis();

            // 通过 mealType 获取其对应的中文用餐时段，用于 OrderPayment 表通过 mealFlag 字段判断用餐时段
            String mealTypeCN = getMealTypeChineseNameFromEnum(menuStoreConfig.getMealType());

            // 0 <= 当前时间戳 - 批量打印时间戳 < 5 分钟，修改订单状态为批量打印状态，并调用打印机相关逻辑
            // todo 可以使用 Math.abs() 方法计算 绝对值
            long round = currentTimestamp - batchPrintTimestamp;
            if (round < 5 * 60 * 1000 && round >= 0) {

                // 根据公司 ID 查询出打印状态为 "未打印"，且订单状态为 "待发货" 或 "待收货" 状态的订单 ID
                // 防止出现 "已取消" 状态的订单调用打印机相关逻辑
                LambdaQueryWrapper<OrderPayment> orderQueryWrapper = new LambdaQueryWrapper<OrderPayment>()
                        .eq(OrderPayment::getBatchPrinted, BatchPrintEnum.NOT_BATCH_PRINTED.getValue())
                        .in(OrderPayment::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue())
                        .eq(OrderPayment::getCompanyId, companyId)
                        .like(OrderPayment::getMealFlag, mealTypeCN)
                        .select(OrderPayment::getId);

                LambdaUpdateWrapper<OrderPayment> orderUpdateWrapper = new LambdaUpdateWrapper<OrderPayment>()
                        .eq(OrderPayment::getBatchPrinted, BatchPrintEnum.NOT_BATCH_PRINTED.getValue())
                        .in(OrderPayment::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue())
                        .eq(OrderPayment::getCompanyId, companyId)
                        .like(OrderPayment::getMealFlag, mealTypeCN);

                List<String> orderIds = orderMapper.selectObjs(orderQueryWrapper);
                if (orderIds != null && !orderIds.isEmpty()) {
                    // 更新批量打印状态
                    OrderPayment orderPayment = new OrderPayment();
                    orderPayment.setBatchPrinted(BatchPrintEnum.BATCH_PRINTED.getValue());
                    int updateCount = orderMapper.update(orderPayment, orderUpdateWrapper);
                    HashMap<String ,String> alreadyPrintedLockerBoxNoOrderIdMap = new HashMap<>();
                    // 调用打印机相关逻辑
                    for (String orderId : orderIds) {
                        try {
                            jxOrderService.printOrderWithFeiEyunPrinter(orderId, alreadyPrintedLockerBoxNoOrderIdMap);
                        } catch (Exception ex) {
                            log.error("打印机打印方法报错：{0}", ex);
                        }
                    }
                    pritedOrderCount = orderIds.size();

                    log.info("changed order_payment.`batch_printed`: 未批量打印小票 -> 已批量打印小票 (2 -> 1), update count: {}", updateCount);
                }
            }
        }
        log.info("storeId: {}, printTime: {}, printed order count: {}", menuStoreConfig.getStoreId(), menuStoreConfig.getPrintType(), pritedOrderCount);
        log.info("changeOrderBatchPrintAndPrintOrder, executed");
    }

    /**
     * 将 16：35 格式的时间转换成当日对应的时间戳
     *
     * @param time hh:mm 格式的时间
     * @return 时间戳
     */
    private long parseTimeToCurrentTimeMillis(String time) {
        // 如果 time 的格式是 hh:mm，则在末尾拼接上 ":00"
        if (time.length() == 5) {
            time += ":00";
        }
        // 获取当前日期
        DateTime now = DateUtil.date();
        // 使用当前日期的年份、月份和日期，并设置时分秒
        DateTime today = DateUtil.parse(now.toDateStr() + " " + time);
        // 转换为时间戳
        return today.getTime();
    }

    /**
     * 将数据库中保存的用餐时段的英文名称转换成对应的中文名称
     *
     * @param mealTypeEn 用餐时段的英文名称
     * @return 中文名称
     */
    private String getMealTypeChineseNameFromEnum(String mealTypeEn) {
        return Arrays.stream(MealTypeEnum.values())
                .filter(mealTypeEnum -> mealTypeEnum.getMealTypeEn().equals(mealTypeEn))
                .map(MealTypeEnum::getMealTypeCn)
                .findAny()
                .orElse("");
    }

}
